Skip to content

Add sendBinary() to WebSocketClient#162

Merged
bgardner8008 merged 3 commits intomasterfrom
MM-67958-calls-websocket
Mar 26, 2026
Merged

Add sendBinary() to WebSocketClient#162
bgardner8008 merged 3 commits intomasterfrom
MM-67958-calls-websocket

Conversation

@bgardner8008
Copy link
Copy Markdown
Contributor

Summary

Adds sendBinary(base64Data: string) to the WebSocketClient API, which sends a binary WebSocket frame with the provided base64-encoded payload. Calls on mattermost-mobile needs to send binary SDP frames over WebSocket during call signaling. Calls previously used SocketRocket/CFStream WebSocket implementation which supported binary sending, no longer usable due to lack of TLS 1.3 support on iOS.

Note: the testing is very superficial, and I considered rolling it back. The mobile repos has testing for this.

Ticket Link

https://mattermost.atlassian.net/browse/MM-67958

Adds sendBinaryDataFor/sendBinary API across iOS (Starscream write(data:)),
Android (OkHttp ByteString), and TypeScript layers. Data is passed as
base64-encoded string over the bridge and decoded natively before sending.

Needed by mattermost-mobile calls WebSocket to send binary SDP frames
(msgpack-encoded) over a URLSession-backed connection, enabling TLS 1.3
support on iOS (SocketRocket/CFStream is capped at TLS 1.2).
- TypeScript: WebSocketClient.test.ts verifies sendBinary() delegates to
  NativeWebSocketClient.sendBinaryDataFor() with the correct url and data.
  Adds transformIgnorePatterns to jest config for validator ESM package.
- Kotlin: WebSocketClientModuleImplTest covers the invalid-URL error path
  (URISyntaxException → promise.reject). Happy-path binary send requires
  android.util.Base64 and a live WebSocket, which are only available in
  instrumented (on-device) tests.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de9c6d72-2276-4227-88cc-596dd8ba398f

📥 Commits

Reviewing files that changed from the base of the PR and between a4e211f and 4c84992.

📒 Files selected for processing (2)
  • src/WebSocketClient/__tests__/WebSocketClient.test.ts
  • src/WebSocketClient/index.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/WebSocketClient/tests/WebSocketClient.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/WebSocketClient/index.tsx

📝 Walkthrough

Walkthrough

This pull request adds a cross-platform API to send Base64-encoded binary data over existing WebSocket connections, with native implementations for Android and iOS, TypeScript bindings, and unit tests validating behavior and error handling.

Changes

Cohort / File(s) Summary
Android Core Implementation
android/src/main/java/com/mattermost/networkclient/WebSocketClientModuleImpl.kt
New public sendBinaryDataFor(wsUrl: String, data: String, promise: Promise) method: parses URI, decodes Base64 to bytes, sends via existing WebSocket as ByteString, resolves or rejects the Promise (catches URISyntaxException and other exceptions).
Android Bridges
android/src/newarch/java/com/WebSocketClientModule.kt, android/src/oldarch/java/com/WebSocketClientModule.kt
Added sendBinaryDataFor entry points: new-arch validates url/data/promise and delegates to implementation; old-arch exposes @ReactMethod that forwards to implementation.
Android Unit Tests
android/src/test/java/com/mattermost/networkclient/WebSocketClientModuleImplTest.kt
New unit test asserting sendBinaryDataFor rejects Promise with URISyntaxException for malformed URL inputs.
iOS Native Implementation
ios/WebSocket/WebSocketClientImpl.mm, ios/WebSocket/WebSocketWrapper.swift
Added exported RN method sendBinaryDataFor (Objective-C bridge) and Swift wrapper implementation: validates URL, locates existing WebSocket, Base64-decodes data, writes binary payload via webSocket.write(data:), and rejects/resolve promise on errors/success.
TypeScript Native Interface
src/WebSocketClient/NativeWebSocketClient.ts
Extended TurboModule Spec with sendBinaryDataFor(url: string, data: string): Promise<void> method.
TypeScript Client & Tests
src/WebSocketClient/index.tsx, src/WebSocketClient/__tests__/WebSocketClient.test.ts, src/types/WebSocketClient.ts
Added sendBinary(data: string) to WebSocketClient delegating to native sendBinaryDataFor; WebSocketClientInterface gains optional sendBinary?; tests verify delegation and exact-call semantics for sendBinary.
Test Config
package.json
Updated Jest transformIgnorePatterns to allow transforms for @react-native, react-native, and validator within node_modules.

Sequence Diagram(s)

sequenceDiagram
  participant JS as WebSocketClient (JS)
  participant TM as NativeWebSocketClient (TurboModule)
  participant NI as NativeImpl (Android/iOS)
  participant Store as WebSocketStore
  participant WS as WebSocket

  JS->>TM: sendBinaryDataFor(url, base64Data)
  TM->>NI: sendBinaryDataFor(url, base64Data, promise)
  NI->>NI: validate URL
  alt invalid URL
    NI-->>TM: reject Promise (URISyntaxException / invalid URL)
    TM-->>JS: Promise rejected
  else valid URL
    NI->>Store: lookup existing WebSocket for url
    alt websocket not found
      NI-->>TM: reject Promise ("no websocket for url")
      TM-->>JS: Promise rejected
    else websocket found
      NI->>NI: Base64 decode data -> bytes
      alt decode fails
        NI-->>TM: reject Promise (base64 decode error)
        TM-->>JS: Promise rejected
      else decode succeeds
        NI->>WS: write(binary bytes)
        WS-->>NI: write success
        NI-->>TM: resolve Promise (null)
        TM-->>JS: Promise resolved
      end
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: adding a sendBinary() method to the WebSocketClient API, which is the primary objective of this pull request.
Description check ✅ Passed The description clearly explains the purpose of adding sendBinary() functionality, the use case for mattermost-mobile call signaling, and the context about replacing deprecated WebSocket implementations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch MM-67958-calls-websocket

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/types/WebSocketClient.ts (1)

44-44: Consider making sendBinary required in the interface.

The implementation now exposes sendBinary, so keeping it optional weakens the public contract and forces unnecessary undefined checks for typed consumers.

Suggested type contract tweak
-    sendBinary?(data: string): void;
+    sendBinary(data: string): void;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/WebSocketClient.ts` at line 44, The WebSocketClient interface
currently declares sendBinary? as optional; change it to a required method by
removing the optional modifier so it reads sendBinary(data: string): void; then
update any implementers to ensure they provide sendBinary (verify
classes/functions implementing WebSocketClient include a sendBinary method).
Look for the WebSocketClient interface declaration and usages/implementations to
remove unnecessary undefined checks and adjust typings accordingly.
android/src/main/java/com/mattermost/networkclient/WebSocketClientModuleImpl.kt (1)

142-157: Inconsistent exception type caught compared to other methods in this file.

The sendBinaryDataFor method catches URISyntaxException (line 146), but all other methods in this file (e.g., sendDataFor, connectFor, disconnectFor) catch IllegalArgumentException when parsing the URI. While URISyntaxException is technically correct for the URI constructor, this inconsistency could cause confusion.

Consider aligning with the existing pattern for consistency, or updating all methods to use URISyntaxException for correctness.

Option A: Align with existing pattern
     fun sendBinaryDataFor(wsUrl: String, data: String, promise: Promise) {
         val wsUri: URI
         try {
             wsUri = URI(wsUrl)
-        } catch (error: URISyntaxException) {
+        } catch (error: IllegalArgumentException) {
             return promise.reject(error)
         }
Option B: Update all methods to use URISyntaxException (broader refactor)

This would be a larger change affecting sendDataFor, connectFor, disconnectFor, invalidateClientFor, ensureClientFor, and createClientFor.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@android/src/main/java/com/mattermost/networkclient/WebSocketClientModuleImpl.kt`
around lines 142 - 157, The URI parsing in sendBinaryDataFor currently catches
URISyntaxException which is inconsistent with other methods; change the
exception type to IllegalArgumentException to match sendDataFor, connectFor,
disconnectFor and other methods (or alternatively update all related methods to
URISyntaxException if you prefer that broader refactor), i.e., modify the
try/catch around URI(wsUrl) in sendBinaryDataFor so it catches
IllegalArgumentException and rejects the promise accordingly; ensure references
to clients[wsUri]!!, webSocket!!.send, and promise.reject/resolve remain
unchanged.
android/src/test/java/com/mattermost/networkclient/WebSocketClientModuleImplTest.kt (1)

27-34: Consider using JUnit assertions for better failure messages.

Using Kotlin's assert() provides less informative failure messages compared to JUnit's assertTrue() or AssertJ's assertions. When tests fail, clearer messages help with debugging.

🧪 Proposed improvement using JUnit assertions
+import org.junit.Assert.assertTrue
+
     `@Test`
     fun `sendBinaryDataFor rejects promise for malformed URL`() {
         impl.sendBinaryDataFor("not a valid uri", "dGVzdA==", promise)

         val captor = ArgumentCaptor.forClass(Throwable::class.java)
         verify(promise).reject(captor.capture())
-        assert(captor.value is URISyntaxException)
+        assertTrue(
+            "Expected URISyntaxException but got ${captor.value::class.simpleName}",
+            captor.value is URISyntaxException
+        )
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@android/src/test/java/com/mattermost/networkclient/WebSocketClientModuleImplTest.kt`
around lines 27 - 34, The test method `sendBinaryDataFor rejects promise for
malformed URL` uses Kotlin's `assert()` which gives poor failure messages;
replace that with a JUnit assertion such as `org.junit.Assert.assertTrue` or
`org.junit.Assert.assertThat` to check the captured exception type (use
`assertTrue(captor.value is URISyntaxException)` or a Hamcrest/assertThat
matcher) so failures report clear diagnostics; update the assertion that
currently reads `assert(captor.value is URISyntaxException)` to a JUnit
assertion and ensure the `captor` and `promise` references (from
`impl.sendBinaryDataFor(...)`, `ArgumentCaptor.forClass(Throwable::class.java)`,
and `verify(promise).reject(captor.capture())`) remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/src/newarch/java/com/WebSocketClientModule.kt`:
- Around line 75-77: The platform-specific rejection comes from treating an
empty payload as missing: in WebSocketClientModule.kt change the null-or-empty
check so empty binary payloads are accepted; replace the condition that uses
data.isNullOrEmpty() with a null-only check (e.g. data == null) so only absent
data rejects, keeping the same promise rejection path (referencing the url,
data, and promise variables in the send/sendBinary method in
WebSocketClientModule).

In `@src/WebSocketClient/__tests__/WebSocketClient.test.ts`:
- Around line 4-29: Update the test file formatting to satisfy ESLint/Prettier:
change all single quotes to double quotes (imports like
getOrCreateWebSocketClient, the jest.mock module string and event string
values), ensure object literals use spaces after braces and commas (the
jest.mock object for __esModule, default, WebSocketReadyState, and
WebSocketEvents), and normalize spacing around import statements and between
top-level statements (affecting getOrCreateWebSocketClient,
NativeWebSocketClient, mockNativeClient, jest.mock, WebSocketReadyState,
WebSocketEvents). After making these textual fixes, run the project's
formatter/linter (npm/yarn lint --fix or prettier --write) to apply consistent
styling.

---

Nitpick comments:
In
`@android/src/main/java/com/mattermost/networkclient/WebSocketClientModuleImpl.kt`:
- Around line 142-157: The URI parsing in sendBinaryDataFor currently catches
URISyntaxException which is inconsistent with other methods; change the
exception type to IllegalArgumentException to match sendDataFor, connectFor,
disconnectFor and other methods (or alternatively update all related methods to
URISyntaxException if you prefer that broader refactor), i.e., modify the
try/catch around URI(wsUrl) in sendBinaryDataFor so it catches
IllegalArgumentException and rejects the promise accordingly; ensure references
to clients[wsUri]!!, webSocket!!.send, and promise.reject/resolve remain
unchanged.

In
`@android/src/test/java/com/mattermost/networkclient/WebSocketClientModuleImplTest.kt`:
- Around line 27-34: The test method `sendBinaryDataFor rejects promise for
malformed URL` uses Kotlin's `assert()` which gives poor failure messages;
replace that with a JUnit assertion such as `org.junit.Assert.assertTrue` or
`org.junit.Assert.assertThat` to check the captured exception type (use
`assertTrue(captor.value is URISyntaxException)` or a Hamcrest/assertThat
matcher) so failures report clear diagnostics; update the assertion that
currently reads `assert(captor.value is URISyntaxException)` to a JUnit
assertion and ensure the `captor` and `promise` references (from
`impl.sendBinaryDataFor(...)`, `ArgumentCaptor.forClass(Throwable::class.java)`,
and `verify(promise).reject(captor.capture())`) remain unchanged.

In `@src/types/WebSocketClient.ts`:
- Line 44: The WebSocketClient interface currently declares sendBinary? as
optional; change it to a required method by removing the optional modifier so it
reads sendBinary(data: string): void; then update any implementers to ensure
they provide sendBinary (verify classes/functions implementing WebSocketClient
include a sendBinary method). Look for the WebSocketClient interface declaration
and usages/implementations to remove unnecessary undefined checks and adjust
typings accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3c6bad77-d8d7-4bd5-8c2b-949fa5f6613e

📥 Commits

Reviewing files that changed from the base of the PR and between 7305814 and a4e211f.

📒 Files selected for processing (11)
  • android/src/main/java/com/mattermost/networkclient/WebSocketClientModuleImpl.kt
  • android/src/newarch/java/com/WebSocketClientModule.kt
  • android/src/oldarch/java/com/WebSocketClientModule.kt
  • android/src/test/java/com/mattermost/networkclient/WebSocketClientModuleImplTest.kt
  • ios/WebSocket/WebSocketClientImpl.mm
  • ios/WebSocket/WebSocketWrapper.swift
  • package.json
  • src/WebSocketClient/NativeWebSocketClient.ts
  • src/WebSocketClient/__tests__/WebSocketClient.test.ts
  • src/WebSocketClient/index.tsx
  • src/types/WebSocketClient.ts

@bgardner8008 bgardner8008 requested a review from enahum March 25, 2026 18:17
Copy link
Copy Markdown
Contributor

@enahum enahum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I can see how some code duplication could be avoided but not a big deal

@enahum
Copy link
Copy Markdown
Contributor

enahum commented Mar 26, 2026

@coderabit resolve

@bgardner8008 bgardner8008 merged commit f599964 into master Mar 26, 2026
5 checks passed
@bgardner8008 bgardner8008 deleted the MM-67958-calls-websocket branch March 26, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants